<?php

declare(strict_types=1);

namespace App\Utils;

class EncryptUtil
{
    /**
     * 基础密钥（实际应用中应存储在环境变量或配置文件中）
     * 
     * @var string
     */
    private static string $baseKey = 'hyperf_secret_key';
    
    /**
     * 签名密钥（实际应用中应存储在环境变量或配置文件中）
     * 
     * @var string
     */
    private static string $signKey = 'signature_key_complex';
    
    /**
     * 密钥有效时间（秒）
     * 
     * @var int
     */
    private static int $keyValidTime = 86400; // 24小时
    
    /**
     * 加密方法
     * 
     * @param array $data 需要加密的数据
     * @param string|null $customSalt 自定义盐值（可选，如设备ID等）
     * @return string 加密后的字符串
     */
    public static function encrypt(array $data, ?string $customSalt = null): string
    {
        // 添加时间戳到数据中
        $timestamp = time();
        $data['_timestamp'] = $timestamp;
        
        // 将数据转换为JSON
        $jsonData = json_encode($data);
        
        // 生成派生密钥
        $derivedKey = self::deriveKey($timestamp, $customSalt);
        
        // 使用AES-256-GCM加密（更安全的加密模式）
        $ivlen = openssl_cipher_iv_length('aes-256-gcm');
        $iv = openssl_random_pseudo_bytes($ivlen);
        $tag = ''; // 认证标签
        
        $encrypted = openssl_encrypt(
            $jsonData, 
            'aes-256-gcm', 
            $derivedKey, 
            OPENSSL_RAW_DATA, 
            $iv, 
            $tag, 
            '', // 附加验证数据
            16 // 标签长度
        );
        
        // 计算签名 (HMAC)
        $signature = hash_hmac('sha256', $encrypted . $iv . $timestamp, self::$signKey, true);
        
        // 将所有数据组合并Base64编码
        $combined = $iv . pack('N', $timestamp) . $tag . $signature . $encrypted;
        
        // 进行多重编码和混淆
        $encoded = base64_encode($combined);
        $encoded = strtr($encoded, '+/', '-_'); // URL安全编码
        
        return $encoded;
    }
    
    /**
     * 解密方法
     * 
     * @param string $encryptedData 加密的数据
     * @param string|null $customSalt 自定义盐值（可选，如设备ID等）
     * @return array 解密后的数据数组
     */
    public static function decrypt(string $encryptedData, ?string $customSalt = null): array
    {
        try {
            // 还原URL安全编码
            $encryptedData = strtr($encryptedData, '-_', '+/');
            
            // Base64解码
            $data = base64_decode($encryptedData);
            throw_error_if(!$data, "无效的加密数据");

            // 提取必要的信息
            $ivlen = openssl_cipher_iv_length('aes-256-gcm');
            $iv = substr($data, 0, $ivlen);
            $timestamp = unpack('N', substr($data, $ivlen, 4))[1];
            $tag = substr($data, $ivlen + 4, 16);
            $signature = substr($data, $ivlen + 4 + 16, 32);
            $encrypted = substr($data, $ivlen + 4 + 16 + 32);
            
            // 验证时间戳是否过期
            $currentTime = time();
            throw_error_if($currentTime - $timestamp > self::$keyValidTime, "加密数据已过期");
            
            // 验证签名
            $expectedSignature = hash_hmac('sha256', $encrypted . $iv . $timestamp, self::$signKey, true);
            throw_error_if(!hash_equals($expectedSignature, $signature), "数据签名验证失败");

            // 生成派生密钥
            $derivedKey = self::deriveKey($timestamp, $customSalt);
            
            // 使用AES-256-GCM解密
            $decrypted = openssl_decrypt(
                $encrypted, 
                'aes-256-gcm', 
                $derivedKey, 
                OPENSSL_RAW_DATA, 
                $iv, 
                $tag
            );

            throw_error_if(!$decrypted, "解密失败");

            // 解析JSON
            $result = json_decode($decrypted, true);
            throw_error_if(!is_array($result), "解析JSON失败");

            // 移除时间戳字段
            unset($result['_timestamp']);
            
            return $result;
        } catch (\Throwable $e) {
            // 可以记录日志
            // \Hyperf\Context\ApplicationContext::getContainer()->get(\Psr\Log\LoggerInterface::class)->error('解密失败: ' . $e->getMessage());
            return [];
        }
    }
    
    /**
     * 基于时间戳和盐值派生密钥
     * 
     * @param int $timestamp 时间戳
     * @param string|null $customSalt 自定义盐值
     * @return string 派生的密钥
     */
    private static function deriveKey(int $timestamp, ?string $customSalt = null): string
    {
        // 创建一个独特的盐值，基于时间和可选的自定义盐值（如设备ID）
        $timeFactor = floor($timestamp / (self::$keyValidTime / 4)); // 每6小时更新一次密钥
        $salt = $timeFactor . ($customSalt ?? '') . self::$baseKey;
        
        // 使用PBKDF2派生密钥
        return hash_pbkdf2(
            'sha256',
            self::$baseKey,
            $salt,
            10000, // 迭代次数
            32,    // 输出长度
            true   // 原始二进制输出
        );
    }
} 